package com.wk.aop;

import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.wk.annotation.syslog;
import com.wk.pojo.CrmLogMessage;
import com.wk.service.TestService;
import com.wk.utils.SnowflakeIdWorker;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;


//切面类  http://my.oschina.net/yangzg/blog/343945
/*
 * 特别注意： Spring的配置文件中添加：
 *
 * <aop:aspectj-autoproxy />
 * spring-mvc-dispatcher.xml中天机
 * <!--通知spring使用cglib而不是jdk的来生成代理方法 AOP可以拦截到Controller-->
 * <aop:aspectj-autoproxy proxy-target-class="true"/>
 *
 * <aop:config>节点中proxy-target-class="true"不为true时。
 * 当登录的时候会报这个异常java.lang.NoSuchMethodException: $Proxy54.login()，
 */

@Slf4j
@Aspect
@Component
public class SysLogAspect {
    SnowflakeIdWorker snowflakeIdWorker = new SnowflakeIdWorker(10, 10);
    @Autowired
    TestService testService;

    // 定义切入点
    @Pointcut("@annotation(com.wk.annotation.syslog)")
    public void syslog() {
        int a = 0;
    }

    /**
     * 环绕通知 用于拦截指定内容，记录用户的操作
     */
    @Around(value = "@annotation(syslog)")
    @Order(0)
    public Object interceptorApplogic(ProceedingJoinPoint pj, syslog syslog) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestURI = request.getRequestURI();
        String description = syslog.description();
        Object[] args = pj.getArgs();
        Signature signature = pj.getSignature();
        List<Object> list = new ArrayList<>();
        if (ArrayUtils.isNotEmpty(args)) {
            for (Object arg : args) {
                if (arg instanceof HttpServletResponse) {
                    continue;
                } else if (arg instanceof HttpServletRequest) {
                    String source = ((HttpServletRequest) arg).getHeader("source");
                } else {
                    list.add(arg);
                }
            }
        }
        String input = JSONArray.toJSONString(list, SerializerFeature.WriteNullStringAsEmpty);
        System.out.println("input:" + input);
        Object proceed = null;
        try {
            proceed = pj.proceed(args);
        } catch (Throwable throwable) {
            throwable.printStackTrace();
        } finally {
            long end = System.currentTimeMillis();
            String output = JSONArray.toJSONString(proceed, SerializerFeature.WriteNullStringAsEmpty);
            System.out.println("output:" + output);
            CrmLogMessage log = new CrmLogMessage();
            log.setContent(input);
            log.setLogid(snowflakeIdWorker.workerId);
            log.setDateTime(end - start);
            log.setResult(output);
            log.setRequestUrl(requestURI);
            log.setRemarks(description);
            System.out.println("CrmLogMessage:" + log.toString());
            testService.insert(log);
        }


        return proceed;
    }


    // 定义切入点  @Pointcut("execution(public * com.jay..*.*(..))")  -- 表示对com.jay 包下的所有方法都可添加切入点
    // @Pointcut("execution(com.wk.controller.*)")
    public void aApplogic() {
    }

    //定义切入点  -- 拦截指定的方法  这里拦截 com.jay.demo3.aop1.impl.UserManagerApplogicImpl 的addOne()方法
    // @Pointcut("execution(public * com.jay..*.addOne(..))")
    public void joinPointForAddOne() {

    }

    /**
     * 环绕通知   拦截指定的切点，这里拦截所有controller请求 切入点根据具体的接口判断
     */
    @Around("execution(* com.wk.controller.*.*(..))")
    @Order(Ordered.HIGHEST_PRECEDENCE)//前置执行
    public Object interceptorRequest(ProceedingJoinPoint pj) throws Throwable {
        long start = System.currentTimeMillis();
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();
        String requestURI = request.getRequestURI();
        Object[] args = pj.getArgs();
        List<Object> list = new ArrayList<>();

        try {
            //幂等实现，根据订单号去记录表查询，查询到直接返回，否则继续执行接口逻辑
            String orderId = request.getParameter("orderId");
            String inter = requestURI.startsWith("/") ? requestURI.substring(1) : requestURI;
            String str = "addOrder,addUser";//需要幂等的接口，可配置在数据库
            System.out.println("orderId=" + orderId + ",str=" + str+ ",inter=" + inter);
            if (StringUtils.isNoneBlank(orderId) && StringUtils.isNoneBlank(inter) && str.contains(inter)) {
                CrmLogMessage log = testService.getOrderId(orderId);
                if (log == null) {
                    if (ArrayUtils.isNotEmpty(args)) {
                        for (Object arg : args) {
                            if (arg instanceof HttpServletResponse) {
                                continue;
                            } else if (arg instanceof HttpServletRequest) {
                                String source = ((HttpServletRequest) arg).getHeader("source");
                            } else {
                                list.add(arg);
                            }
                        }
                    }
                    String input = JSONArray.toJSONString(list, SerializerFeature.WriteNullStringAsEmpty);
                    //执行接口逻辑，并记录结果
                    Object proceed = pj.proceed(args);
                    long end = System.currentTimeMillis();
                    String output = JSONArray.toJSONString(proceed, SerializerFeature.WriteNullStringAsEmpty);
                    System.out.println("output:" + output);
                    CrmLogMessage log1 = new CrmLogMessage();
                    log1.setContent(input);
                    log1.setLogid(snowflakeIdWorker.workerId);
                    log1.setDateTime(end - start);
                    log1.setResult(output);
                    log1.setRequestUrl(requestURI);
                    log1.setRemarks(orderId);//订单表示
                    System.out.println("CrmLogMessage:" + log1.toString());
                    testService.insert(log1);
                    return proceed;
                } else {//存在幂等直接返回
                    String result = log.getResult();
                    return result;
                }
            }
            // 不需要幂等的接口，直接请求接口

        } catch (Exception e) {
            // 异常处理记录日志..log.error(e);
            throw e;
        }
        return pj.proceed(args);
    }


    // 获取方法的中文备注____用于记录用户的操作日志描述
    public static String getMthodRemark(ProceedingJoinPoint joinPoint)
            throws Exception {
        String targetName = joinPoint.getTarget().getClass().getName();
        String methodName = joinPoint.getSignature().getName();
        System.out.println("====调用" + methodName + "方法-开始！");
        Object[] arguments = joinPoint.getArgs();   //获得参数列表
        System.out.println("打印出方法调用时传入的参数，可以在这里通过添加参数的类型，进行一些简易逻辑处理和判断");
        if (arguments.length <= 0) {
            System.out.println("=== " + methodName + " 方法没有参数");
        } else {
            for (int i = 0; i < arguments.length; i++) {
                System.out.println("==== 参数   " + (i + 1) + " : " + arguments[i]);
            }
        }

        Class targetClass = Class.forName(targetName);
        Method[] method = targetClass.getMethods();
        String methode = "";
        for (Method m : method) {
            if (m.getName().equals(methodName)) {
                Class[] tmpCs = m.getParameterTypes();
                if (tmpCs.length == arguments.length) {
                    syslog methodCache = m.getAnnotation(syslog.class);
                    methode = methodCache.description();
                    break;
                }
            }
        }
        return methode;
    }
}